<?php

/**
 * @info Watajax JQUERY plugin server side
 * @author West Art Communication AB
 * @version 1.1
 * @changelog
 * 		20100106 1.1 - Fixed UTF encoding issue
 * 		20100106 1.0 - First release
 * @see http://code.google.com/p/watajax/
 */
abstract class Watajax {

	public $columns = array();
	public $data = array();
	public $perPage = 10;
	public $page = 1;
	public $sortBy = NULL;
	public $sortOrder = "ASC";
	public $search = "";
	public $errorMsg = "Din s&ouml;kning gav inga resultat.";
	public $classes = array();

	public $row_tag = "tr";
	public $column_tag = "td";
	public $header_column_tag = "th";

	public function __construct() {
		$this->perPage = (is_numeric($_GET["watajax_per_page"])) ? $_GET["watajax_per_page"] : 10;
		$this->page = (is_numeric($_GET["watajax_page"])) ? $_GET["watajax_page"] : 1;
		$this->sortBy = ($_GET["watajax_sortBy"] != "") ? $_GET["watajax_sortBy"] : NULL;
		$this->sortOrder = ($_GET["watajax_sortOrder"] != "") ? $_GET["watajax_sortOrder"] : "ASC";
		$this->search = ($_GET["watajax_search"] != "") ? $_GET["watajax_search"] : "";
		$this->filter = ($_GET["watajax_filter"] != "") ? $_GET["watajax_filter"] : "";
	}

	public function run() {
		$this->checkForAction();
	}

	public function checkForAction() {
		switch ($_GET["action"]) {
			case "watajax_load_head":
				$this->sendHead();
				break;
			case "watajax_load_settings":
				echo json_encode($this->getSettings());
				break;
			case "watajax_load_body":
				$this->sendBody();
				break;
			case "watajax_load_csv":
				$this->sendCSV();
				break;
			default:
				break;
		}
	}

	public function getFilter() {
		$filter = array();
		if (is_array($this->columns)) {
			foreach ($this->columns as $id => $data) {
				if ($data["filter"] == "select" || $data["filter"] == "text") {
					$filter[$id]["type"] = $data["filter"];
					if ($data["filter"] == "select") {
						if (is_array($data["select_filter_values"])) {
							$filter[$id]["contents"] = $data["select_filter_values"];
							$filter[$id]["contents_override"] = true;
						} else {
							$filter[$id]["contents"] = $this->getGroupedColumnData($id);
						}
					}
				}
			}
		}
		if (count($filter) > 0) {
			$filter["watajax_has_filters"] = true;
		}
		return $filter;
	}

	public function getSettings() {
		$page_count = $this->getNumberOfPages();
		return array("pages" => $page_count["pages"],
			"items" => $page_count["items"],
			"filters" => $this->getFilter()
		);
	}

	abstract public function getGroupedColumnData($column);

	abstract public function sortData();

	abstract public function getNumberOfPages();

	abstract public function getData($ignore_pages = false);

	abstract public function searchFilterData();

	/**
	 * Adds a class to the TR if condition in $test is med
	 * @param string $class
	 * @param string $column
	 * @param eval_expression $test
	 */
	public function addClass($class, $column, $test) {
		$this->classes[$column][] = array("test" => $test, "class" => $class);
	}

	public function renderClass($row_data) {
		$class = array();
		foreach ($this->classes as $column => $data) {
			foreach ($data as $test) {
				$column_data = $row_data[$column];
				if (eval($test["test"])) {
					if (!in_array($test["class"], $class)) {
						$class[] = $test["class"];
					}
				}
			}
		}
		if (count($class) > 0) {
			return ' class="' . implode(" ", $class) . '"';
		}
	}

	public function sendBody() {
		$this->sortData();
		$this->searchFilterData();
		$sorted_data = $this->getData();
		if (count($sorted_data) <= 0) {
			echo "no_rows_found";
		} else {
			foreach ($sorted_data as $row_id => $row_data) {
				echo "<".$this->row_tag." id='" . $_GET["watajax_table"] . "_row_$row_id'" . $this->renderClass($row_data) . ">";
				foreach ($this->columns as $column_id => $column_data) {
					if ($this->columns[$column_id]["hide"] != true) {
						echo "<".$this->column_tag." id='" . $column_id . "_data'>" . $row_data[$column_id] . "</".$this->column_tag.">";
					}
				}
				echo "</".$this->row_tag.">";
			}
		}
	}

	public function sendCSV($all_pages = true) {
		$delimiter = "\t";
		$header = "";
		$row = "";

		// Header
		header("Content-type: application/csv");
		header("Content-Disposition: attachment; filename=" . $_GET["watajax_table"] . ".csv");
		header("Pragma: no-cache");
		header("Expires: 0");

		foreach ($this->columns as $id => $data) {
			if ($data["hide"] != true) {
				$header .= html_entity_decode($data["name"]) . $delimiter;
			}
		}
		echo rtrim($header, $delimiter) . PHP_EOL;

		$this->sortData();
		$this->searchFilterData();
		$sorted_data = $this->getData(true);
		if (count($sorted_data) > 0) {
			foreach ($sorted_data as $row_id => $row_data) {
				$row = "";
				foreach ($this->columns as $column_id => $column_data) {
					if ($this->columns[$column_id]["hide"] != true) {
						$row .= html_entity_decode(strip_tags($row_data[$column_id] . $delimiter));
					}
				}
				echo rtrim($row, $delimiter) . PHP_EOL;
			}
		}
	}

	public function sendHead() {
		echo "<".$this->row_tag.">";
		foreach ($this->columns as $id => $data) {
			if ($data["hide"] != true) {
				$class = ($data["virtual"] == true) ? "virtual" : "";
				echo "<".$this->header_column_tag." id='$id' class='$class'>" . $data["name"] . "</".$this->header_column_tag.">";
			}
		}
		echo "</".$this->row_tag.">";
	}

	public function getAppliedFilters() {
		$appliedFilters = array();
		if ($this->filter != "") {
			$where = "";
			$filters = explode("|", $this->filter);

			if (is_array($filters) && count($filters) > 0) {
				foreach ($filters as $f) {
					list($column, $value) = explode(":", $f);
					$appliedFilters[$column] = $value;
				}
			}
		}
		return (count($appliedFilters) > 0) ? $appliedFilters : false;
	}

	public function transformColumn($col, $data, $row) {
		$replace = array();
		$replacements = array();

		foreach (array_keys($this->columns) as $k) {
			$replace[] = "!" . $k;
			$replacements[] = $row[$k];
		}

		if ($this->columns[$col]["transform"] != "") {
			$data = str_replace($replace, $replacements, $this->columns[$col]["transform"]);
			if ($this->columns[$col]["eval_transform"] == true || $this->columns[$col]["eval_transform"] == '1') {
				$data = eval($data);
			}
		}
		return $data;
	}

	function check_utf8($str) {
		$len = @strlen($str);
		for ($i = 0; $i < $len; $i++) {
			$c = ord($str[$i]);
			if ($c > 128) {
				if (($c > 247))
					return false;
				elseif ($c > 239)
					$bytes = 4;
				elseif ($c > 223)
					$bytes = 3;
				elseif ($c > 191)
					$bytes = 2;
				else
					return false;
				if (($i + $bytes) > $len)
					return false;
				while ($bytes > 1) {
					$i++;
					$b = ord($str[$i]);
					if ($b < 128 || $b > 191)
						return false;
					$bytes--;
				}
			}
		}
		return true;
	}

}

class WatajaxSql extends Watajax {

	public $query = "";
	protected $database_table = "";
	protected $query_sort = "";
	protected $encoding = "UTF-8";
	protected $where = "";
	protected $sqlTables = "";

	public function __construct($encoding = "UTF-8") {
		parent::__construct();
		$this->encoding = $encoding;
		$this->table = $_GET["watajax_table"];
	}

	public function addWhere($where) {
		if ($this->where == "") {
			$this->where = " WHERE ";
		} else {
			$this->where .= " AND ";
		}
		$this->where .= $where . " ";
	}

	public function addSqlTable($table) {
		if ($this->sqlTables == "") {
			$this->sqlTables = ",";
		}
		$this->sqlTables .= $table;
	}

	public function searchFilterData() {
		if ($this->search != "") {
			$where = "";
			foreach ($this->columns as $key => $value) {
				if ($value["virtual"] != true) {
					$table = (isset($this->columns[$key]["table"])) ? $this->columns[$key]["table"] : $this->table;
					$s = ($this->encoding == 'UTF-8' && $this->check_utf8($value)) ? utf8_decode($this->search) : $this->search;
					$where .= "`".$table."`.`$key` LIKE '%" . $s . "%' OR ";
				}
			}
			$where = "(" . rtrim($where, " OR ") . ")";
			$this->addWhere($where);
		}
		if ($appliedFilters = $this->getAppliedFilters()) {
			$where = "";
			foreach ($appliedFilters as $column => $value) {
				$table = (isset($this->columns[$column]["table"])) ? $this->columns[$column]["table"] : $this->table;
				$s = ($this->encoding == 'UTF-8' && $this->check_utf8($value)) ? utf8_decode($value) : $value;
				$where .= "`".$table."`.`" . mysql_real_escape_string($column) . "` = '" . $s . "' AND ";
			}
			$where = "(" . rtrim($where, " AND ") . ")";
			$this->addWhere($where);
		}
	}

	public function sortData() {
		if ($this->sortBy != "") {
			$order = (strtoupper($this->sortOrder) == "ASC") ? "ASC" : "DESC";
			$table = (isset($this->columns[$this->sortBy]["table"])) ? $this->columns[$this->sortBy]["table"] : $this->table;
			$this->query_sort = " ORDER BY `$table`.`" . mysql_real_escape_string($this->sortBy) . "` " . $order;
		}
	}

	public function encode($arr) {
		$encoded = array();
		if ($this->encoding == "UTF-8") {
			foreach ($arr as $key => $val) {
				$encoded[$key] = utf8_encode($val);
			}
		} else {
			$encoded = $arr;
		}
		return $encoded;
	}

	public function getGroupedColumnData($column) {
		$column_data = array();
		$sql = "SELECT `$column` FROM `" . mysql_real_escape_string($this->table) . "`" . $this->sqlTables . $this->where . " GROUP BY `$column` ORDER BY `$column`";
		$r = mysql_query($sql);
		while ($data = mysql_fetch_row($r)) {
			$column_data[] = ($this->encoding = 'UTF-8') ? utf8_encode($data[0]) : $data[0];
		}
		return $column_data;
	}

	public function assebleColumns($columns) {
		$columns_sql = "";
		foreach ($columns as $c) {
			if (stripos($c, ".") === false) {
				$columns_sql .= "`" . $this->table . "`.`" . $c . "`,";
			} else {
				$columns_sql .= $c . ",";
			}
		}
		return rtrim($columns_sql, ",");
	}

	public function getData($ignore_pages = false) {
		$data = array();
		$limit_start = (($this->page - 1) * $this->perPage);
		$columns = array();
		foreach ($this->columns as $key => $value) {
			if ($value["virtual"] != true) {
				$prefix = ($value["table"] != "") ? $value["table"] . "." : "";
				$columns[] = $prefix . $key;
			}
		}
		if ($ignore_pages == false) {
			$limit = " LIMIT " . $limit_start . "," . $this->perPage;
		}
		$sql = "SELECT " . $this->assebleColumns($columns) . " FROM `" . mysql_real_escape_string($this->table) . "`" . $this->sqlTables . $this->where . $this->query_sort . $limit;
		$this->query = $sql;
		$r = mysql_query($sql);
		while ($row = @mysql_fetch_assoc($r)) {
			$fixed_row = array();
			$row = $this->encode($row);
			foreach ($this->columns as $key => $value) {
				$fixed_row[$key] = $this->transformColumn($key, $row[$key], $row);
			}
			$data[] = $fixed_row;
		}
		return $data;
	}

	function getNumberOfPages() {
		$this->searchFilterData();
		$sql = "SELECT COUNT(*) FROM `".mysql_real_escape_string($this->table)."`".$this->sqlTables.$this->where;
		$num = @mysql_result(mysql_query($sql), 0);
		$page_num = (is_numeric($num)) ? ceil($num / $this->perPage) : 0;
		return array("pages" => $page_num, "items" => $num);
	}

}

class WatajaxArray extends Watajax {

	public function __construct() {
		parent::__construct();
	}

	public function getNumberOfPages() {
		return array("pages" => ceil(count($this->data) / $this->perPage), "items" => count($this->data));
	}

	public function getData($ignore_pages = false) {
		if ($ignore_pages == false) {
			foreach($this->data as $real_key => $row){
			    foreach($row as $key => $value) {
				$fixed_row[$real_key][$key] = $this->transformColumn($key, $value, $row);
			    }
			}

			return array_slice($this->data, (($this->page - 1) * $this->perPage), $this->perPage);

		} else {
			return $this->data;
		}
	}

	public function searchFilterData() {
		if ($this->search != "") {
			$this->data = $this->arr_search($this->data, $this->search);
		}
		if ($filter = $this->getAppliedFilters()) {
			$this->data = $this->filter_data($this->data, $filter);
		}
	}

	public function getGroupedColumnData($column) {
		$this->searchFilterData();
		$column_data[] = array();
		foreach ($this->data as $row) {
			$column_data[] = $row[$column];
		}
		return array_unique($column_data);
	}

	function filter_data($data, $filter) {
		$result = array();
		foreach ($data as $row_id => $row_data) {
			foreach ($filter as $col => $filtered_value) {
				if ($row_data[$col] != $filtered_value) {
					continue 2;
				}
			}
			$result[$row_id] = $row_data;
		}
		return $result;
	}

	function arr_search($data, $query) {
		$result = array();
		foreach ($data as $row_id => $row_data) {
			foreach ($row_data as $column) {
				if (stripos($column, $query) !== false) {
					$result[$row_id] = $row_data;
				}
			}
		}
		return $result;
	}

	public function sortData() {
		if ($this->sortBy != NULL) {
			$f = 'strcasecmp';
			$arr = $this->data;
			$l = $this->sortBy;
			if ($this->columns[$l]['sort_type'] == "numeric") {
				$function = "return (preg_replace('@\D@','',trim(strip_tags(\$a[$l]))) > preg_replace('@\D@','',trim(strip_tags(\$b[$l]))));";
			} else {
				$function = "return $f(trim(strip_tags(\$a[$l])), trim(strip_tags(\$b[$l])));";
			}
			usort($arr, create_function('$a, $b', $function));
			$this->data = (strtoupper($this->sortOrder) == "ASC") ? $arr : array_reverse($arr);
		}
	}

	public function transformArray() {
		foreach ($this->data as $val) {
			$fixed_row = array();
			foreach ($this->columns as $key => $col) {
				$fixed_row[$key] = $this->transformColumn($key, $val[$key], $val);
			}
			$data[] = $fixed_row;
		}
		return $this->data = $data;
	}

}

?>